'    Copyright 2008 Jan Molnar <jan.molnar@abds.de>
'
'    This file is part of JCQ.

'    JCQ is free software: you can redistribute it and/or modify
'    it under the terms of the GNU General Public License as published by
'    the Free Software Foundation, either version 2 of the License, or
'    (at your option) any later version.

'    JCQ is distributed in the hope that it will be useful,
'    but WITHOUT ANY WARRANTY; without even the implied warranty of
'    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
'    GNU General Public License for more details.

'    You should have received a copy of the GNU General Public License
'    along with JCQ. If not, see <http://www.gnu.org/licenses/>.

Public Class TcpContext
    Implements ITcpContext, IDisposable

    Private _Client As System.Net.Sockets.TcpClient
    Private ReadOnly _lock As System.Object = New System.Object
    Private _IsConnected As Boolean

    Private _BytesReceived As Long
    Private _BytesSent As Long

    Private ReadOnly _Id As String = String.Concat("TcpContext:", Guid.NewGuid.ToString)

    Private _ConnectionCloseExpected As Boolean

    Public ReadOnly Property Id() As String Implements ITcpContext.Id
        Get
            Return _Id
        End Get
    End Property

    Public ReadOnly Property IsConnected() As Boolean Implements ITcpContext.IsConnected
        Get
            Return _IsConnected
        End Get
    End Property

    Public ReadOnly Property BytesReceived() As Long Implements ITcpContext.BytesReceived
        Get
            Return _BytesReceived
        End Get
    End Property

    Public ReadOnly Property BytesSent() As Long Implements ITcpContext.BytesSent
        Get
            Return _BytesSent
        End Get
    End Property

    Public ReadOnly Property ConnectionCloseExpected() As Boolean Implements ITcpContext.ConnectionCloseExpected
        Get
            Return _ConnectionCloseExpected
        End Get
    End Property

    Public Sub SetCloseExpected() Implements ITcpContext.SetCloseExpected
        SyncLock _lock
            _ConnectionCloseExpected = True
            Debug.WriteLine(String.Format("{0} expecting disconnect.", Environment.TickCount), _Id)
        End SyncLock
    End Sub

    Public Sub SetCloseUnexpected() Implements ITcpContext.SetCloseUnexpected
        SyncLock _lock
            _ConnectionCloseExpected = False
            Debug.WriteLine(String.Format("{0} expecting alive.", Environment.TickCount), _Id)
        End SyncLock
    End Sub

    Public Sub Connect(ByVal endPoint As System.Net.IPEndPoint) Implements ITcpContext.Connect
        Dim stream As System.Net.Sockets.NetworkStream
        Dim info As ReceiveInfo

        Debug.WriteLine(String.Format("Connecting to {0}.", endPoint), _Id)

        _Client = New System.Net.Sockets.TcpClient
        _Client.Connect(endPoint)

        _IsConnected = True

        stream = _Client.GetStream()

        info = New ReceiveInfo(stream)

        stream.BeginRead(info.buffer, 0, ReceiveInfo.BUFFERSIZE, AddressOf ReadCallback, info)
    End Sub

    Public Sub Disconnect() Implements ITcpContext.Disconnect
        If Not _IsConnected Then Return

        _Client.Close()
        OnDisconnected()
    End Sub

    Private Sub ReadCallback(ByVal ar As IAsyncResult)
        Dim info As ReceiveInfo = DirectCast(ar.AsyncState, ReceiveInfo)
        Dim data As List(Of Byte)

        Try
            SyncLock _lock
                Dim dataSize As Integer

                dataSize = info.Stream.EndRead(ar)
                data = New List(Of Byte)(info.buffer)
                data = data.GetRange(0, dataSize)

                Do While info.Stream.DataAvailable
                    Dim buffer As Byte() = New Byte(ReceiveInfo.BUFFERSIZE - 1) {}

                    dataSize = info.Stream.Read(buffer, 0, ReceiveInfo.BUFFERSIZE)
                    data.AddRange(New List(Of Byte)(buffer).GetRange(0, dataSize))
                Loop

                info = New ReceiveInfo(info.Stream)

                If data.Count > 0 Then
                    info.Stream.BeginRead(info.buffer, 0, ReceiveInfo.BUFFERSIZE, AddressOf ReadCallback, info)

                    System.Threading.ThreadPool.QueueUserWorkItem(New System.Threading.WaitCallback(AddressOf AsyncRaiseDataReceived), data)
                Else
                    Debug.WriteLine("Transfer loop ended with no data.", _Id)
                    OnDisconnected()
                End If
            End SyncLock
        Catch ex As Exception
            JCsTools.Core.Kernel.Exceptions.PublishException(ex)
            OnDisconnected()
        End Try
    End Sub

    Private Sub AsyncRaiseDataReceived(ByVal state As Object)
        Dim data As List(Of Byte)

        Try
            data = DirectCast(state, List(Of Byte))

            Threading.Interlocked.Add(_BytesReceived, data.Count)

            OnDataReceived(data)
        Catch ex As Exception
            Core.Kernel.Exceptions.PublishException(ex)
            OnDisconnected()
        End Try
    End Sub

    Public Sub SendData(ByVal data As System.Collections.Generic.List(Of Byte)) Implements ITcpContext.SendData
        If data Is Nothing Then Throw New ArgumentNullException("data")
        If data.Count = 0 Then Throw New ArgumentException("List<Byte> does not contain data.")

        Dim buffer As Byte()

        Try
            buffer = data.ToArray
            SyncLock _lock
                Dim stream As System.Net.Sockets.NetworkStream

                stream = _Client.GetStream

                stream.Write(buffer, 0, buffer.Length)
            End SyncLock

            Threading.Interlocked.Add(_BytesSent, buffer.Length)
        Catch
            OnDisconnected()
            Throw
        End Try
    End Sub

    Private Sub OnDataReceived(ByVal data As IEnumerable(Of Byte))
        RaiseEvent DataReceived(Me, New DataReceivedEventArgs(data))
    End Sub

    Private Sub OnConnected(ByVal endpoint As System.Net.IPEndPoint)
        RaiseEvent Connected(Me, New ConnectedEventArgs(endpoint))
    End Sub

    Private Sub OnDisconnected()
        Dim expected As Boolean

        If Not _IsConnected Then Return

        _IsConnected = False
        expected = ConnectionCloseExpected

        If expected Then
            Debug.WriteLine(String.Format("{0} Connection closed (expected).", Environment.TickCount), _Id)
        Else
            Debug.WriteLine(String.Format("{0} Connection closed (unexpected).", Environment.TickCount), _Id)
        End If

        RaiseEvent Disconnected(Me, New DisconnectEventArgs(expected))
    End Sub

    Public Event DataReceived(ByVal sender As Object, ByVal e As DataReceivedEventArgs) Implements ITcpContext.DataReceived

    Public Event Connected(ByVal sender As Object, ByVal e As ConnectedEventArgs) Implements ITcpContext.Connected

    Public Event Disconnected(ByVal sender As Object, ByVal e As DisconnectEventArgs) Implements ITcpContext.Disconnected

    Private disposedValue As Boolean = False        ' To detect redundant calls

    ' IDisposable
    Protected Overridable Sub Dispose(ByVal disposing As Boolean)
        If Not Me.disposedValue Then
            If disposing Then
                If _Client IsNot Nothing Then _Client.Close()
            End If
        End If
        Me.disposedValue = True
    End Sub

#Region " IDisposable Support "
    ' This code added by Visual Basic to correctly implement the disposable pattern.
    Public Sub Dispose() Implements IDisposable.Dispose
        ' Do not change this code.  Put cleanup code in Dispose(ByVal disposing As Boolean) above.
        Dispose(True)
        GC.SuppressFinalize(Me)
    End Sub
#End Region

End Class

Friend Class ReceiveInfo
    Public Const BUFFERSIZE As Integer = 2048

    Public Sub New(ByVal stream As System.Net.Sockets.NetworkStream)
        buffer = New Byte(BUFFERSIZE - 1) {}
        _Stream = stream
    End Sub

    Private _Stream As System.Net.Sockets.NetworkStream
    Public ReadOnly Property Stream() As System.Net.Sockets.NetworkStream
        Get
            Return _Stream
        End Get
    End Property


    Friend buffer As Byte()
End Class